{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ce8457ed-c0b1-4a74-abbd-9d3d2211270f",
   "metadata": {},
   "source": [
    "# Migrating off ConversationTokenBufferMemory\n",
    "\n",
    "Follow this guide if you're trying to migrate off one of the old memory classes listed below:\n",
    "\n",
    "\n",
    "| Memory Type                      | Description                                                                                                                                                       |\n",
    "|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n",
    "| `ConversationTokenBufferMemory`  | Keeps only the most recent messages in the conversation under the constraint that the total number of tokens in the conversation does not exceed a certain limit. |\n",
    "\n",
    "`ConversationTokenBufferMemory` applies additional processing on top of the raw conversation history to trim the conversation history to a size that fits inside the context window of a chat model. \n",
    "\n",
    "This processing functionality can be accomplished using LangChain's built-in [trimMessages](https://api.js.langchain.com/functions/_langchain_core.messages.trimMessages.html) function."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79935247-acc7-4a05-a387-5d72c9c8c8cb",
   "metadata": {},
   "source": [
    "```{=mdx}\n",
    ":::important\n",
    "\n",
    "We’ll begin by exploring a straightforward method that involves applying processing logic to the entire conversation history.\n",
    "\n",
    "While this approach is easy to implement, it has a downside: as the conversation grows, so does the latency, since the logic is re-applied to all previous exchanges in the conversation at each turn.\n",
    "\n",
    "More advanced strategies focus on incrementally updating the conversation history to avoid redundant processing.\n",
    "\n",
    "For instance, the LangGraph [how-to guide on summarization](https://langchain-ai.github.io/langgraphjs/how-tos/add-summary-conversation-history/) demonstrates\n",
    "how to maintain a running summary of the conversation while discarding older messages, ensuring they aren't re-processed during later turns.\n",
    ":::\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d07f9459-9fb6-4942-99c9-64558aedd7d4",
   "metadata": {},
   "source": [
    "## Set up\n",
    "\n",
    "### Dependencies\n",
    "\n",
    "```{=mdx}\n",
    "import Npm2Yarn from \"@theme/Npm2Yarn\"\n",
    "\n",
    "<Npm2Yarn>\n",
    "  @langchain/openai @langchain/core zod\n",
    "</Npm2Yarn>\n",
    "```\n",
    "\n",
    "### Environment variables\n",
    "\n",
    "```typescript\n",
    "process.env.OPENAI_API_KEY = \"YOUR_OPENAI_API_KEY\";\n",
    "```\n",
    "\n",
    "```{=mdx}\n",
    "<details open>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ce2d951",
   "metadata": {},
   "source": [
    "## Reimplementing ConversationTokenBufferMemory logic\n",
    "\n",
    "Here, we'll use `trimMessages` to keeps the system message and the most recent messages in the conversation under the constraint that the total number of tokens in the conversation does not exceed a certain limit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e1550bee",
   "metadata": {},
   "outputs": [],
   "source": [
    "import {\n",
    "  AIMessage,\n",
    "  HumanMessage,\n",
    "  SystemMessage,\n",
    "} from \"@langchain/core/messages\";\n",
    "\n",
    "const messages = [\n",
    "  new SystemMessage(\"you're a good assistant, you always respond with a joke.\"),\n",
    "  new HumanMessage(\"i wonder why it's called langchain\"),\n",
    "  new AIMessage(\n",
    "    'Well, I guess they thought \"WordRope\" and \"SentenceString\" just didn\\'t have the same ring to it!'\n",
    "  ),\n",
    "  new HumanMessage(\"and who is harrison chasing anyways\"),\n",
    "  new AIMessage(\n",
    "      \"Hmmm let me think.\\n\\nWhy, he's probably chasing after the last cup of coffee in the office!\"\n",
    "  ),\n",
    "  new HumanMessage(\"why is 42 always the answer?\"),\n",
    "  new AIMessage(\n",
    "      \"Because it's the only number that's constantly right, even when it doesn't add up!\"\n",
    "  ),\n",
    "  new HumanMessage(\"What did the cow say?\"),\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6442f74b-2c36-48fd-a3d1-c7c5d18c050f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SystemMessage {\n",
      "  \"content\": \"you're a good assistant, you always respond with a joke.\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {}\n",
      "}\n",
      "HumanMessage {\n",
      "  \"content\": \"and who is harrison chasing anyways\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {}\n",
      "}\n",
      "AIMessage {\n",
      "  \"content\": \"Hmmm let me think.\\n\\nWhy, he's probably chasing after the last cup of coffee in the office!\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {},\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": []\n",
      "}\n",
      "HumanMessage {\n",
      "  \"content\": \"why is 42 always the answer?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {}\n",
      "}\n",
      "AIMessage {\n",
      "  \"content\": \"Because it's the only number that's constantly right, even when it doesn't add up!\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {},\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": []\n",
      "}\n",
      "HumanMessage {\n",
      "  \"content\": \"What did the cow say?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {}\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "import { trimMessages } from \"@langchain/core/messages\";\n",
    "import { ChatOpenAI } from \"@langchain/openai\";\n",
    "\n",
    "const selectedMessages = await trimMessages(\n",
    "  messages,\n",
    "  {\n",
    "    // Please see API reference for trimMessages for other ways to specify a token counter.\n",
    "    tokenCounter: new ChatOpenAI({ model: \"gpt-4o\" }),\n",
    "    maxTokens: 80,  // <-- token limit\n",
    "    // The startOn is specified\n",
    "    // to make sure we do not generate a sequence where\n",
    "    // a ToolMessage that contains the result of a tool invocation\n",
    "    // appears before the AIMessage that requested a tool invocation\n",
    "    // as this will cause some chat models to raise an error.\n",
    "    startOn: \"human\",\n",
    "    strategy: \"last\",\n",
    "    includeSystem: true,  // <-- Keep the system message\n",
    "  }\n",
    ")\n",
    "\n",
    "for (const msg of selectedMessages) {\n",
    "    console.log(msg);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f05d272-2d22-44b7-9fa6-e617a48584b4",
   "metadata": {},
   "source": [
    "```{=mdx}\n",
    "</details>\n",
    "```\n",
    "\n",
    "## Modern usage with LangGraph\n",
    "\n",
    "The example below shows how to use LangGraph to add simple conversation pre-processing logic.\n",
    "\n",
    "```{=mdx}\n",
    ":::note\n",
    "\n",
    "If you want to avoid running the computation on the entire conversation history each time, you can follow\n",
    "the [how-to guide on summarization](https://langchain-ai.github.io/langgraphjs/how-tos/add-summary-conversation-history/) that demonstrates\n",
    "how to discard older messages, ensuring they aren't re-processed during later turns.\n",
    "\n",
    ":::\n",
    "```\n",
    "\n",
    "```{=mdx}\n",
    "<details open>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "05d360e0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hi! I'm bob\n",
      "Hello, Bob! How can I assist you today?\n",
      "what was my name?\n",
      "You mentioned that your name is Bob. How can I help you today?\n"
     ]
    }
   ],
   "source": [
    "import { v4 as uuidv4 } from 'uuid';\n",
    "import { ChatOpenAI } from \"@langchain/openai\";\n",
    "import { StateGraph, MessagesAnnotation, END, START, MemorySaver } from \"@langchain/langgraph\";\n",
    "import { trimMessages } from \"@langchain/core/messages\";\n",
    "\n",
    "// Define a chat model\n",
    "const model = new ChatOpenAI({ model: \"gpt-4o\" });\n",
    "\n",
    "// Define the function that calls the model\n",
    "const callModel = async (state: typeof MessagesAnnotation.State): Promise<Partial<typeof MessagesAnnotation.State>> => {\n",
    "  // highlight-start\n",
    "  const selectedMessages = await trimMessages(\n",
    "    state.messages,\n",
    "    {\n",
    "      tokenCounter: (messages) => messages.length, // Simple message count instead of token count\n",
    "      maxTokens: 5, // Allow up to 5 messages\n",
    "      strategy: \"last\",\n",
    "      startOn: \"human\",\n",
    "      includeSystem: true,\n",
    "      allowPartial: false,\n",
    "    }\n",
    "  );\n",
    "  // highlight-end\n",
    "\n",
    "  const response = await model.invoke(selectedMessages);\n",
    "\n",
    "  // With LangGraph, we're able to return a single message, and LangGraph will concatenate\n",
    "  // it to the existing list\n",
    "  return { messages: [response] };\n",
    "};\n",
    "\n",
    "\n",
    "// Define a new graph\n",
    "const workflow = new StateGraph(MessagesAnnotation)\n",
    "// Define the two nodes we will cycle between\n",
    "  .addNode(\"model\", callModel)\n",
    "  .addEdge(START, \"model\")\n",
    "  .addEdge(\"model\", END)\n",
    "\n",
    "const app = workflow.compile({\n",
    "  // Adding memory is straightforward in LangGraph!\n",
    "  // Just pass a checkpointer to the compile method.\n",
    "  checkpointer: new MemorySaver()\n",
    "});\n",
    "\n",
    "// The thread id is a unique key that identifies this particular conversation\n",
    "// ---\n",
    "// NOTE: this must be `thread_id` and not `threadId` as the LangGraph internals expect `thread_id`\n",
    "// ---\n",
    "const thread_id = uuidv4();\n",
    "const config = { configurable: { thread_id }, streamMode: \"values\" as const };\n",
    "\n",
    "const inputMessage = {\n",
    "  role: \"user\",\n",
    "  content: \"hi! I'm bob\",\n",
    "}\n",
    "for await (const event of await app.stream({ messages: [inputMessage] }, config)) {\n",
    "  const lastMessage = event.messages[event.messages.length - 1];\n",
    "  console.log(lastMessage.content);\n",
    "}\n",
    "\n",
    "// Here, let's confirm that the AI remembers our name!\n",
    "const followUpMessage = {\n",
    "  role: \"user\",\n",
    "  content: \"what was my name?\",\n",
    "}\n",
    "\n",
    "// ---\n",
    "// NOTE: You must pass the same thread id to continue the conversation\n",
    "// we do that here by passing the same `config` object to the `.stream` call.\n",
    "// ---\n",
    "for await (const event of await app.stream({ messages: [followUpMessage] }, config)) {\n",
    "  const lastMessage = event.messages[event.messages.length - 1];\n",
    "  console.log(lastMessage.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84229e2e-a578-4b21-840a-814223406402",
   "metadata": {},
   "source": [
    "```{=mdx}\n",
    "</details>\n",
    "```\n",
    "\n",
    "## Usage with a pre-built langgraph agent\n",
    "\n",
    "This example shows usage of an Agent Executor with a pre-built agent constructed using the [createReactAgent](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html) function.\n",
    "\n",
    "If you are using one of the [old LangChain pre-built agents](https://js.langchain.com/v0.1/docs/modules/agents/agent_types/), you should be able\n",
    "to replace that code with the new [LangGraph pre-built agent](https://langchain-ai.github.io/langgraphjs/how-tos/create-react-agent/) which leverages\n",
    "native tool calling capabilities of chat models and will likely work better out of the box.\n",
    "\n",
    "```{=mdx}\n",
    "<details open>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9e54ccdc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hi! I'm bob. What is my age?\n",
      "\n",
      "42 years old\n",
      "Hi Bob! You are 42 years old.\n",
      "do you remember my name?\n",
      "Yes, your name is Bob! If there's anything else you'd like to know or discuss, feel free to ask.\n"
     ]
    }
   ],
   "source": [
    "import { z } from \"zod\";\n",
    "import { v4 as uuidv4 } from 'uuid';\n",
    "import { BaseMessage, trimMessages } from \"@langchain/core/messages\";\n",
    "import { tool } from \"@langchain/core/tools\";\n",
    "import { ChatOpenAI } from \"@langchain/openai\";\n",
    "import { MemorySaver } from \"@langchain/langgraph\";\n",
    "import { createReactAgent } from \"@langchain/langgraph/prebuilt\";\n",
    "\n",
    "const getUserAge = tool(\n",
    "  (name: string): string => {\n",
    "    // This is a placeholder for the actual implementation\n",
    "    if (name.toLowerCase().includes(\"bob\")) {\n",
    "      return \"42 years old\";\n",
    "    }\n",
    "    return \"41 years old\";\n",
    "  },\n",
    "  {\n",
    "    name: \"get_user_age\",\n",
    "    description: \"Use this tool to find the user's age.\",\n",
    "    schema: z.string().describe(\"the name of the user\"),\n",
    "  }\n",
    ");\n",
    "\n",
    "const memory = new MemorySaver();\n",
    "const model2 = new ChatOpenAI({ model: \"gpt-4o\" });\n",
    "\n",
    "// highlight-start\n",
    "const stateModifier = async (messages: BaseMessage[]): Promise<BaseMessage[]> => {\n",
    "  // We're using the message processor defined above.\n",
    "  return trimMessages(\n",
    "    messages,\n",
    "    {\n",
    "      tokenCounter: (msgs) => msgs.length, // <-- .length will simply count the number of messages rather than tokens\n",
    "      maxTokens: 5, // <-- allow up to 5 messages.\n",
    "      strategy: \"last\",\n",
    "      // The startOn is specified\n",
    "      // to make sure we do not generate a sequence where\n",
    "      // a ToolMessage that contains the result of a tool invocation\n",
    "      // appears before the AIMessage that requested a tool invocation\n",
    "      // as this will cause some chat models to raise an error.\n",
    "      startOn: \"human\",\n",
    "      includeSystem: true, // <-- Keep the system message\n",
    "      allowPartial: false,\n",
    "    }\n",
    "  );\n",
    "};\n",
    "// highlight-end\n",
    "\n",
    "const app2 = createReactAgent({\n",
    "  llm: model2,\n",
    "  tools: [getUserAge],\n",
    "  checkpointSaver: memory,\n",
    "  // highlight-next-line\n",
    "  messageModifier: stateModifier,\n",
    "});\n",
    "\n",
    "// The thread id is a unique key that identifies\n",
    "// this particular conversation.\n",
    "// We'll just generate a random uuid here.\n",
    "const threadId2 = uuidv4();\n",
    "const config2 = { configurable: { thread_id: threadId2 }, streamMode: \"values\" as const };\n",
    "\n",
    "// Tell the AI that our name is Bob, and ask it to use a tool to confirm\n",
    "// that it's capable of working like an agent.\n",
    "const inputMessage2 = {\n",
    "  role: \"user\",\n",
    "  content: \"hi! I'm bob. What is my age?\",\n",
    "}\n",
    "\n",
    "for await (const event of await app2.stream({ messages: [inputMessage2] }, config2)) {\n",
    "  const lastMessage = event.messages[event.messages.length - 1];\n",
    "  console.log(lastMessage.content);\n",
    "}\n",
    "\n",
    "// Confirm that the chat bot has access to previous conversation\n",
    "// and can respond to the user saying that the user's name is Bob.\n",
    "const followUpMessage2 = {\n",
    "  role: \"user\",\n",
    "  content: \"do you remember my name?\",\n",
    "};\n",
    "\n",
    "for await (const event of await app2.stream({ messages: [followUpMessage2] }, config2)) {\n",
    "  const lastMessage = event.messages[event.messages.length - 1];\n",
    "  console.log(lastMessage.content);\n",
    "}"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "f4d16e09-1d90-4153-8576-6d3996cb5a6c",
   "metadata": {},
   "source": [
    "```{=mdx}\n",
    "</details>\n",
    "```\n",
    "\n",
    "## LCEL: Add a preprocessing step\n",
    "\n",
    "The simplest way to add complex conversation management is by introducing a pre-processing step in front of the chat model and pass the full conversation history to the pre-processing step.\n",
    "\n",
    "This approach is conceptually simple and will work in many situations; for example, if using a [RunnableWithMessageHistory](/docs/how_to/message_history/) instead of wrapping the chat model, wrap the chat model with the pre-processor.\n",
    "\n",
    "The obvious downside of this approach is that latency starts to increase as the conversation history grows because of two reasons:\n",
    "\n",
    "1. As the conversation gets longer, more data may need to be fetched from whatever store your'e using to store the conversation history (if not storing it in memory).\n",
    "2. The pre-processing logic will end up doing a lot of redundant computation, repeating computation from previous steps of the conversation.\n",
    "\n",
    "```{=mdx}\n",
    ":::caution\n",
    "\n",
    "If you want to use a chat model's tool calling capabilities, remember to bind the tools to the model before adding the history pre-processing step to it!\n",
    "\n",
    ":::\n",
    "```\n",
    "\n",
    "```{=mdx}\n",
    "<details open>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "a1c8adf2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AB6uzWscxviYlbADFeDlnwIH82Fzt\",\n",
      "  \"content\": \"\",\n",
      "  \"additional_kwargs\": {\n",
      "    \"tool_calls\": [\n",
      "      {\n",
      "        \"id\": \"call_TghBL9dzqXFMCt0zj0VYMjfp\",\n",
      "        \"type\": \"function\",\n",
      "        \"function\": \"[Object]\"\n",
      "      }\n",
      "    ]\n",
      "  },\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"completionTokens\": 16,\n",
      "      \"promptTokens\": 95,\n",
      "      \"totalTokens\": 111\n",
      "    },\n",
      "    \"finish_reason\": \"tool_calls\",\n",
      "    \"system_fingerprint\": \"fp_a5d11b2ef2\"\n",
      "  },\n",
      "  \"tool_calls\": [\n",
      "    {\n",
      "      \"name\": \"what_did_the_cow_say\",\n",
      "      \"args\": {},\n",
      "      \"type\": \"tool_call\",\n",
      "      \"id\": \"call_TghBL9dzqXFMCt0zj0VYMjfp\"\n",
      "    }\n",
      "  ],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"input_tokens\": 95,\n",
      "    \"output_tokens\": 16,\n",
      "    \"total_tokens\": 111\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "import { ChatOpenAI } from \"@langchain/openai\";\n",
    "import { AIMessage, HumanMessage, SystemMessage, BaseMessage, trimMessages } from \"@langchain/core/messages\";\n",
    "import { tool } from \"@langchain/core/tools\";\n",
    "import { z } from \"zod\";\n",
    "\n",
    "const model3 = new ChatOpenAI({ model: \"gpt-4o\" });\n",
    "\n",
    "const whatDidTheCowSay = tool(\n",
    "  (): string => {\n",
    "    return \"foo\";\n",
    "  },\n",
    "  {\n",
    "    name: \"what_did_the_cow_say\",\n",
    "    description: \"Check to see what the cow said.\",\n",
    "    schema: z.object({}),\n",
    "  }\n",
    ");\n",
    "\n",
    "// highlight-start\n",
    "const messageProcessor = trimMessages(\n",
    "  {\n",
    "    tokenCounter: (msgs) => msgs.length, // <-- .length will simply count the number of messages rather than tokens\n",
    "    maxTokens: 5, // <-- allow up to 5 messages.\n",
    "    strategy: \"last\",\n",
    "    // The startOn is specified\n",
    "    // to make sure we do not generate a sequence where\n",
    "    // a ToolMessage that contains the result of a tool invocation\n",
    "    // appears before the AIMessage that requested a tool invocation\n",
    "    // as this will cause some chat models to raise an error.\n",
    "    startOn: \"human\",\n",
    "    includeSystem: true, // <-- Keep the system message\n",
    "    allowPartial: false,\n",
    "  }\n",
    ");\n",
    "// highlight-end\n",
    "\n",
    "// Note that we bind tools to the model first!\n",
    "const modelWithTools = model3.bindTools([whatDidTheCowSay]);\n",
    "\n",
    "// highlight-next-line\n",
    "const modelWithPreprocessor = messageProcessor.pipe(modelWithTools);\n",
    "\n",
    "const fullHistory = [\n",
    "  new SystemMessage(\"you're a good assistant, you always respond with a joke.\"),\n",
    "  new HumanMessage(\"i wonder why it's called langchain\"),\n",
    "  new AIMessage('Well, I guess they thought \"WordRope\" and \"SentenceString\" just didn\\'t have the same ring to it!'),\n",
    "  new HumanMessage(\"and who is harrison chasing anyways\"),\n",
    "  new AIMessage(\"Hmmm let me think.\\n\\nWhy, he's probably chasing after the last cup of coffee in the office!\"),\n",
    "  new HumanMessage(\"why is 42 always the answer?\"),\n",
    "  new AIMessage(\"Because it's the only number that's constantly right, even when it doesn't add up!\"),\n",
    "  new HumanMessage(\"What did the cow say?\"),\n",
    "];\n",
    "\n",
    "// We pass it explicitly to the modelWithPreprocessor for illustrative purposes.\n",
    "// If you're using `RunnableWithMessageHistory` the history will be automatically\n",
    "// read from the source that you configure.\n",
    "const result = await modelWithPreprocessor.invoke(fullHistory);\n",
    "console.log(result);"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "5da7225a-5e94-4f53-bb0d-86b6b528d150",
   "metadata": {},
   "source": [
    "```{=mdx}\n",
    "</details>\n",
    "```\n",
    "\n",
    "If you need to implement more efficient logic and want to use `RunnableWithMessageHistory` for now the way to achieve this\n",
    "is to subclass from [BaseChatMessageHistory](https://api.js.langchain.com/classes/_langchain_core.chat_history.BaseChatMessageHistory.html) and\n",
    "define appropriate logic for `addMessages` (that doesn't simply append the history, but instead re-writes it).\n",
    "\n",
    "Unless you have a good reason to implement this solution, you should instead use LangGraph."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2717810",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "Explore persistence with LangGraph:\n",
    "\n",
    "* [LangGraph quickstart tutorial](https://langchain-ai.github.io/langgraphjs/tutorials/quickstart/)\n",
    "* [How to add persistence (\"memory\") to your graph](https://langchain-ai.github.io/langgraphjs/how-tos/persistence/)\n",
    "* [How to manage conversation history](https://langchain-ai.github.io/langgraphjs/how-tos/manage-conversation-history/)\n",
    "* [How to add summary of the conversation history](https://langchain-ai.github.io/langgraphjs/how-tos/add-summary-conversation-history/)\n",
    "\n",
    "Add persistence with simple LCEL (favor LangGraph for more complex use cases):\n",
    "\n",
    "* [How to add message history](/docs/how_to/message_history/)\n",
    "\n",
    "Working with message history:\n",
    "\n",
    "* [How to trim messages](/docs/how_to/trim_messages)\n",
    "* [How to filter messages](/docs/how_to/filter_messages/)\n",
    "* [How to merge message runs](/docs/how_to/merge_message_runs/)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TypeScript",
   "language": "typescript",
   "name": "tslab"
  },
  "language_info": {
   "codemirror_mode": {
    "mode": "typescript",
    "name": "javascript",
    "typescript": true
   },
   "file_extension": ".ts",
   "mimetype": "text/typescript",
   "name": "typescript",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
